package com.example.demo.use_demo.a_excel_demo.utils;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.XML;
import com.alibaba.fastjson2.JSON;
import com.example.demo.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.HashedMap;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.ss.usermodel.PictureData;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xssf.usermodel.*;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * 读取Excel图片工具类（支持浮动图片和嵌入式图片），通过将Excel文件解析为压缩文件并读取其中的xml配置文件从而读取Excel中的嵌入式图片
 * @author zhangqiang
 * @version 1.0
 * @date 2025/02/18
 */
@Slf4j
public class ExcelPicReadUtil {

    /**
     * 获取 WPS 文档中的图片，包括嵌入式图片和浮动式图片。
     *
     * @param excelByteData 二进制数据（一般情况下Excel可能需要被二次读取，如果是通过流读取则读取后指针会移动到文件末尾，无法再次读取,而转换成byte后可以被多次读取）
     * @return 图片信息的 map（图片Excel位置描述或者Excel-ID）
     */
    public static Map<String, XSSFPictureData> getPictures(byte[] excelByteData) {
        try {
            // 获取嵌入式图片的配置信息，如ID、名称等
            Map<String, String> pictureConfig = processZipEntries(new ByteArrayInputStream(excelByteData));
            // 获取嵌入式图片
            Map<String, XSSFPictureData> mapPictures = processPictures(new ByteArrayInputStream(excelByteData), pictureConfig);
            // 获取浮动图片
            Iterator<Sheet> sheetIterator = WorkbookFactory.create(new ByteArrayInputStream(excelByteData)).sheetIterator();
            while (sheetIterator.hasNext()) {
                mapPictures.putAll(getFloatingPictures((XSSFSheet) sheetIterator.next()));
            }
            return mapPictures;
        } catch (IOException e) {
            return new HashedMap<>();
        }
    }

    /**
     * 浮动图片处理：
     * 获取浮动图片，以 map 形式返回，键为行列格式 x-y。
     *
     * @param xssfSheet WPS 工作表
     * @return 浮动图片的 map
     */
    private static Map<String, XSSFPictureData> getFloatingPictures(XSSFSheet xssfSheet) {
        Map<String, XSSFPictureData> mapFloatingPictures = new HashMap<>();
        XSSFDrawing drawingPatriarch = xssfSheet.getDrawingPatriarch();
        if (drawingPatriarch != null) {
            List<XSSFShape> shapes = drawingPatriarch.getShapes();
            for (XSSFShape shape : shapes) {
                if (shape instanceof XSSFPicture) {
                    XSSFClientAnchor anchor = (XSSFClientAnchor) shape.getAnchor();
                    XSSFPictureData pictureData = ((XSSFPicture) shape).getPictureData();
                    String key = anchor.getRow1() + "-" + anchor.getCol1();
                    mapFloatingPictures.put(key, pictureData);
                }
            }
        }
        return mapFloatingPictures;
    }

    /**
     * 嵌入式图片处理：
     * 处理 WPS 文件中的图片数据，返回图片信息 map。
     *
     * @param stream    输入流
     * @param pictureConfig 配置映射
     * @return 图片信息的 map
     * @throws IOException 异常 异常
     */
    private static Map<String, XSSFPictureData> processPictures(ByteArrayInputStream stream, Map<String, String> pictureConfig) throws IOException {
        Map<String, XSSFPictureData> mapPictures = new HashedMap<>();
        Workbook workbook = WorkbookFactory.create(stream);
        List<XSSFPictureData> allPictures = (List<XSSFPictureData>) workbook.getAllPictures();
        for (XSSFPictureData pictureData : allPictures) {
            PackagePartName partName = pictureData.getPackagePart().getPartName();
            String uri = partName.getURI().toString();
            if (pictureConfig.containsKey(uri)) {
                String strId = pictureConfig.get(uri);
                mapPictures.put(strId, pictureData);
            }
        }
        return mapPictures;
    }

    /**
     * 嵌入式图片处理：
     * 处理 Zip 文件中的条目，更新图片配置信息。
     *
     * @param stream Zip 输入流
     * @return 配置信息的 map
     * @throws IOException 异常 异常
     */
    private static Map<String, String> processZipEntries(ByteArrayInputStream stream) throws IOException {
        Map<String, String> mapConfig = new HashedMap<>();
        ZipInputStream zipInputStream = new ZipInputStream(stream);
        ZipEntry zipEntry;
        while ((zipEntry = zipInputStream.getNextEntry()) != null) {
            try {
                final String fileName = zipEntry.getName();
                if ("xl/cellimages.xml".equals(fileName)) {
                    processCellImages(zipInputStream, mapConfig);
                } else if ("xl/_rels/cellimages.xml.rels".equals(fileName)) {
                    return processCellImagesRels(zipInputStream, mapConfig);
                }
            } finally {
                zipInputStream.closeEntry();
            }
        }
        return new HashedMap<>();
    }

    /**
     * 嵌入式图片处理：
     * 处理 Zip 文件中的 cellimages.xml 文件，更新图片配置信息。
     *
     * @param zipInputStream Zip 输入流
     * @param mapConfig      配置信息的 map
     * @throws IOException 异常
     */
    private static void processCellImages(ZipInputStream zipInputStream, Map<String, String> mapConfig) throws IOException {
        String content = IOUtils.toString(zipInputStream, StandardCharsets.UTF_8);
        JSONObject jsonObject = XML.toJSONObject(content);
        if (jsonObject != null) {
            JSONObject cellImages = jsonObject.getJSONObject("etc:cellImages");
            if (cellImages != null) {
                JSONArray cellImageArray = null;
                Object cellImage = cellImages.get("etc:cellImage");
                if (cellImage instanceof JSONArray) {
                    cellImageArray = (JSONArray) cellImage;
                } else if (cellImage instanceof JSONObject) {
                    cellImageArray = new JSONArray();
                    cellImageArray.add(cellImage);
                }
                if (cellImageArray != null) {
                    processImageItems(cellImageArray, mapConfig);
                }
            }
        }
    }

    /**
     * 嵌入式图片处理：
     * 处理 cellImageArray 中的图片项，更新图片配置信息。
     *
     * @param cellImageArray 图片项的 JSONArray
     * @param mapConfig      配置信息的 map
     */
    private static void processImageItems(JSONArray cellImageArray, Map<String, String> mapConfig) {
        for (int i = 0; i < cellImageArray.size(); i++) {
            JSONObject imageItem = cellImageArray.getJSONObject(i);
            if (imageItem != null) {
                JSONObject pic = imageItem.getJSONObject("xdr:pic");
                if (pic != null) {
                    processPic(pic, mapConfig);
                }
            }
        }
    }

    /**
     * 处理 pic 中的图片信息，更新图片配置信息。
     *
     * @param pic       图片的 JSONObject
     * @param mapConfig 配置信息的 map
     */
    private static void processPic(JSONObject pic, Map<String, String> mapConfig) {
        JSONObject nvPicPr = pic.getJSONObject("xdr:nvPicPr");
        if (nvPicPr != null) {
            JSONObject cNvPr = nvPicPr.getJSONObject("xdr:cNvPr");
            if (cNvPr != null) {
                String name = cNvPr.getStr("name");
                if (StringUtils.isNotEmpty(name)) {
                    String strImageEmbed = updateImageEmbed(pic);
                    if (strImageEmbed != null) {
                        mapConfig.put(strImageEmbed, name);
                    }
                }
            }
        }
    }

    /**
     * 获取嵌入式图片的 embed 信息。
     *
     * @param pic 图片的 JSONObject
     * @return embed 信息
     */
    private static String updateImageEmbed(JSONObject pic) {
        JSONObject blipFill = pic.getJSONObject("xdr:blipFill");
        if (blipFill != null) {
            JSONObject blip = blipFill.getJSONObject("a:blip");
            if (blip != null) {
                return blip.getStr("r:embed");
            }
        }
        return null;
    }

    /**
     * 嵌入式图片处理：
     * 处理 Zip 文件中的 relationship 条目，更新配置信息。
     *
     * @param zipInputStream Zip 输入流
     * @param mapConfig      配置信息的 map
     * @return 配置信息的 map
     * @throws IOException 异常
     */
    private static Map<String, String> processCellImagesRels(ZipInputStream zipInputStream, Map<String, String> mapConfig) throws IOException {
        String content = IOUtils.toString(zipInputStream, StandardCharsets.UTF_8);
        JSONObject jsonObject = XML.toJSONObject(content);
        JSONObject relationships = jsonObject.getJSONObject("Relationships");
        if (relationships != null) {
            JSONArray relationshipArray = null;
            Object relationship = relationships.get("Relationship");

            if (relationship instanceof JSONArray) {
                relationshipArray = (JSONArray) relationship;
            } else if (relationship instanceof JSONObject) {
                relationshipArray = new JSONArray();
                relationshipArray.add(relationship);
            }
            if (relationshipArray != null) {
                return processRelationships(relationshipArray, mapConfig);
            }
        }
        return null;
    }

    /**
     * 嵌入式图片处理：
     * 处理 relationshipArray 中的关系项，更新配置信息。
     *
     * @param relationshipArray 关系项的 JSONArray
     * @param mapConfig         配置信息的 map
     * @return 配置信息的 map
     */
    private static Map<String, String> processRelationships(JSONArray relationshipArray, Map<String, String> mapConfig) {
        Map<String, String> mapRelationships = new HashedMap<>();
        for (int i = 0; i < relationshipArray.size(); i++) {
            JSONObject relaItem = relationshipArray.getJSONObject(i);
            if (relaItem != null) {
                String id = relaItem.getStr("Id");
                String value = "/xl/" + relaItem.getStr("Target");
                if (mapConfig.containsKey(id)) {
                    String strImageId = mapConfig.get(id);
                    mapRelationships.put(value, strImageId);
                }
            }
        }
        return mapRelationships;
    }

    /**
     * @param file 数据文件
     * @return {@link byte[]}
     */
    public static byte[] getFileStream(File file) {
        try (InputStream inputStream = new FileInputStream(file)) {
            // 创建 ByteArrayOutputStream 来暂存流数据
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            // 将 inputStream 读取到 byteArrayOutputStream 中
            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, length);
            }
            // 将 byteArrayOutputStream 的内容获取为字节数组
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            throw new BusinessException("IO异常" + e.getMessage());
        }
    }

    /**
     * @param inputStream 数据文件流
     * @return {@link byte[]}
     */
    public static byte[] getFileStream(InputStream inputStream) {
        try {
            // 创建 ByteArrayOutputStream 来暂存流数据
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            // 将 inputStream 读取到 byteArrayOutputStream 中
            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, length);
            }
            // 将 byteArrayOutputStream 的内容获取为字节数组
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            throw new BusinessException("IO异常" + e.getMessage());
        }
    }

    /**
     * 读取并保存excel图片
     * @param excelByteData excel
     * @return 图片Excel位置或者图片Excel ID - 图片名称
     */
    public static Map<String,String> readAndSaveImage(byte[] excelByteData){
        // 读取项目图片
        Map<String, XSSFPictureData> pictureDataMap = ExcelPicReadUtil.getPictures(excelByteData);
        // 获取上传路径
        String uploadDirPath = "D:/files/image/";
        // 创建目录
        File mkFile = new File(uploadDirPath);
        boolean dirExist = mkFile.exists() || mkFile.mkdirs();
        log.info("创建目录{}-{}",mkFile.getAbsolutePath(),dirExist);
        // 保存图片
        Map<String,String> picExcelIdNameMap = new HashMap<>(pictureDataMap.size());
        String dateNow = DateUtil.format(DateTime.now(), DatePattern.PURE_DATE_FORMAT);
        for(Map.Entry<String,XSSFPictureData> entry : pictureDataMap.entrySet()){
            String picId = entry.getKey();
            XSSFPictureData pictureData = entry.getValue();

            String fileName = pictureData.getPackagePart().getPartName().getName();
            fileName = dateNow + System.currentTimeMillis() + fileName.substring(fileName.lastIndexOf(StrUtil.DOT));
            String filePath = uploadDirPath + fileName;

            byte[] picDataByte = pictureData.getData();
            try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
                outputStream.write(picDataByte);
                picExcelIdNameMap.put(picId,fileName);
            } catch (IOException e){
                log.error("图片保存异常",e);
                throw new BusinessException("图片保存异常 ID=" + picId);
            }
        }
        return picExcelIdNameMap;
    }

    /**
     * 图片下载
     * @param fileName 文件名称
     * @param response 响应流
     */
    public static void downloadImage(String fileName, HttpServletResponse response) throws Exception {
        String filePath = "D:/files/image/" + fileName;
        response.reset();
        response.setContentType("application/octet-stream");
        response.addHeader("Content-Disposition","attachment; filename=" + URLEncoder.encode(fileName,"UTF-8"));
        // 自动关闭流
        try (InputStream inputStream = new FileInputStream(filePath); ServletOutputStream outputStream = response.getOutputStream()) {
            byte[] bytes = new byte[1024];
            int len;
            // 将输入流写出缓冲字节中，然后写入响应流，读到末尾返回-1
            while ((len = inputStream.read(bytes)) > 0){
                outputStream.write(bytes,0,len);
            }
            outputStream.flush();
        } catch (Exception e){
            log.error("文件下载异常",e);
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            response.getWriter().println(JSON.toJSONString("下载失败"));
        }
    }
}
